/*
   Copyright (c) 2010, The AgreGo Team <team@agrego.net>
   All rights reserved.

   Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE

 This project has begun as an "Epitech Innovative Project" in Epitech, France.
*/
#include "headers/xiniparser.h"
#include "headers/log_save.h"
#include "headers/handshake.h"
#include "headers/connexion.h"

/**
 * Not blocking
 * Creates a son process which will be used to tell on parent process
 * wether the handshake is done or not (in order to establish link)
 * Son process will be waited in the sigusr1Handler function
 * TODO put minutes to wait in double
 *
 * @brief	runs then handshake
 * @param	{connex} The connection to open
 * @return	0 if the handshake fails, 1 otherwise
 **/
void	doHandshake(struct Connexion *connex)
{
	int	i;
	int	sonPid;
	int 	pipefd[2];
	int	waveNumber;
	int 	attemptNumber;
	int	minutesToWait;
	int	defaultWaitValues[3] = {5, 20, 60};
	int	defaultAttemptValues[3] = {4, 3, -1};

	char 	arg[50];

	if (pipe(pipefd) == -1)
	{
		log_write(LOG_EMERG, "Unable to create a pipe %s", strerror(errno));
		log_write(LOG_EMERG, "Son process and Handshake couldn't be done");
		return ;
	}

	if ((sonPid = fork()) == 0)
	{
		xclose(pipefd[0]);
		sonPid = getpid();
		log_write(LOG_INFO, "Created son [pid : %d] to handle handshake", sonPid);

		for (waveNumber = 1; waveNumber <= WAVES_NUMBER; waveNumber++)
		{
			sprintf(arg, "Connection_rehabilitation:wave_%d_tries", waveNumber);
			attemptNumber = xiniparser_getint(arg, defaultAttemptValues[waveNumber - 1]);
			i = 0;
			/* attemptNumber could be -1 which means infinite loop until handshake is ok */
			while (attemptNumber == -1 || i < attemptNumber)
			{
				if (checkMyHand(connex))
				{
					enableConnAfterHandshake(pipefd);
				}

				log_write(LOG_WARNING, "Bad handshake. attempt number %d", i);
				sprintf(arg, "Connection_rehabilitation:wave_%d_attempt_time", waveNumber);
				minutesToWait = xiniparser_getint(arg, defaultWaitValues[waveNumber -1]);

				if (minutesToWait != 0) {
					sleep(minutesToWait * 60);
				}
				i++;
			}
		}
	}
	xclose(pipefd[1]);
	connex->pid = sonPid;
	connex->pipe = pipefd[0];
}


/**
 * Enables a connexion
 * @param {pipefd}	A pipefd array
 * @todo replace the message "a" by a macro
 */
void	enableConnAfterHandshake(int *pipefd)
{
	xwrite(pipefd[1], "a", 1); /* enable the connection */
	xclose(pipefd[1]);
	log_write(LOG_NOTICE, "Exiting son process");
	exit(EXIT_SUCCESS);
}

/**
 * Generates a public key
 * @param {publicKey} should be previously malloced with SHA_DIGEST_LENGTH * sizeof(unsigned char)
 */
void	genPublicKey(unsigned char *publicKey)
{
	time_t	t;
	struct tm	*tmp;
	char	strTime[PUBKEY_LENGTH + 1];

	t = time(NULL);
	tmp = localtime(&t);
	if (tmp == NULL)
	{
		log_write(LOG_ERR, "Can't generate the publicKey. Pid: %d kill", getpid());
		exit(EXIT_FAILURE);
	}
	strftime(strTime, PUBKEY_LENGTH + 1, PUBKEY_FORMAT, tmp);
	log_write(LOG_DEBUG, "publicKey: %s, %d", strTime, strlen(strTime));
	memcpy((void *)publicKey, (void *)strTime, PUBKEY_LENGTH);
}

/**
 * Gets the hash using the public and private key
 * @param {publicKey} public key generated by genPublicKey()
 * @param {hash} unsigned char * previously malloced with SHA_DIGEST_LENGTH * sizeof(unsigned char)
 */
void	getHash(const unsigned char *publicKey, unsigned char *hash)
{
	unsigned char 	*strToHash;
	char 	*privateKey;
	int 	len;

	privateKey = xiniparser_getstring("Network:private_key", "YahouhImASuperPrivateKeyOfTheDeath");
	log_write(LOG_DEBUG,  "Using privateKey: %s", privateKey);
	len = strlen(privateKey);

	strToHash = xmalloc((PUBKEY_LENGTH + len) * sizeof(unsigned char));
	memcpy(strToHash, privateKey, len);
	memcpy(strToHash + len, publicKey, PUBKEY_LENGTH);

 	SHA1(strToHash, (PUBKEY_LENGTH + len) * sizeof(unsigned char), hash);
	xfree(strToHash);
}

/**
 * check a hash with a public key
 *
 * @todo check strncmp
 * @param {publicKey} the public key
 * @param {hashToCheck} the hash to check
 */
int	checkHash(const unsigned char *publicKey, const unsigned char *hashToCheck)
{
	unsigned char	*hashGen;
	int	ret;

	hashGen = xmalloc(SHA_DIGEST_LENGTH * sizeof(unsigned char));
	getHash(publicKey, hashGen);
	ret = 0;
	if (strncmp((char *)hashGen, (char *)hashToCheck, SHA_DIGEST_LENGTH) == 0)
	{
		ret = 1;
	}
/*	while (ret < SHA_DIGEST_LENGTH && *(hashGen + ret) && *(hashToCheck + ret))
	{
		if (*(hashGen + ret) != *(hashToCheck + ret))
		{
			break;
		}
		ret++;
	}*/
	xfree(hashGen);
	return ret;
}

/**
 * Does a select with a timeout
 * @todo replace select by poll (to have poll everywhere)
 * @param {sock} the socket which waits for a reading
 */
int	waitForRead(int sock)
{
	fd_set	fdread;
	struct timeval	tv;
	int ret;

	tv.tv_sec = 5;
	tv.tv_usec = 0;

	FD_ZERO(&fdread);
	FD_SET(sock, &fdread);

	ret = select(sock + 1, &fdread, NULL, NULL, &tv);
	if (ret == -1)
	{
		log_write(LOG_WARNING, "select error(waitForRead in handshake process): %s", strerror(errno));
		return 0;
	}
	return ret;
}
